/*
 * 
 *  This file is part of Sourceprojects.org Lycia Copyright 2010 noctarius
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * 
 */
package org.sourceprojects.lycia.internal.generator;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.xml.XMLConstants;

import org.sourceprojects.lycia.DefinitionBuilder;
import org.sourceprojects.lycia.LyciaConfigurator;
import org.sourceprojects.lycia.LyciaParser;
import org.sourceprojects.lycia.ParserContext;
import org.sourceprojects.lycia.QualifiedName;
import org.sourceprojects.lycia.TypeConverter;
import org.sourceprojects.lycia.annotations.Attribute;
import org.sourceprojects.lycia.annotations.Base64;
import org.sourceprojects.lycia.annotations.Child;
import org.sourceprojects.lycia.annotations.ContextObject;
import org.sourceprojects.lycia.annotations.Nullable;
import org.sourceprojects.lycia.annotations.Parent;
import org.sourceprojects.lycia.annotations.Tag;
import org.sourceprojects.lycia.annotations.TextBody;
import org.sourceprojects.lycia.annotations.XPath;
import org.sourceprojects.lycia.exceptions.LyciaConfigurationException;
import org.sourceprojects.lycia.exceptions.LyciaParsingException;
import org.sourceprojects.lycia.internal.AnnotationCombination;
import org.sourceprojects.lycia.internal.configuration.TagConfigurationUtils;
import org.sourceprojects.lycia.internal.dataconverter.ByteDataConverter;
import org.sourceprojects.lycia.internal.dom.AttrImpl;
import org.sourceprojects.lycia.internal.logging.LyciaLoggerWrapper;
import org.sourceprojects.lycia.internal.platform.AttributeNameFindingStrategy;
import org.sourceprojects.lycia.internal.platform.ParameterMatcher;
import org.sourceprojects.lycia.internal.platform.PlatformSupportUtils;
import org.sourceprojects.lycia.internal.privileged.SetAccessible;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class MethodParameterUtils {

	private static final LyciaLoggerWrapper LOGGER = LyciaLoggerWrapper
			.getLogger(MethodParameterUtils.class);

	@SuppressWarnings("unchecked")
	private static final AnnotationCombination[] LEGAL_ANNOTATION_COMBINATIONS = {
			new AnnotationCombination(ContextObject.class),
			new AnnotationCombination(Base64.class),

			new AnnotationCombination(Attribute.class),
			new AnnotationCombination(Attribute.class, Base64.class),

			new AnnotationCombination(TextBody.class),
			new AnnotationCombination(TextBody.class, Base64.class),

			new AnnotationCombination(Parent.class),
			new AnnotationCombination(Parent.class, TextBody.class),
			new AnnotationCombination(Parent.class, Base64.class),
			new AnnotationCombination(Parent.class, TextBody.class,
					Base64.class),
			new AnnotationCombination(Parent.class, Attribute.class),
			new AnnotationCombination(Parent.class, Attribute.class,
					Base64.class),

			new AnnotationCombination(Child.class),
			new AnnotationCombination(Child.class, TextBody.class),
			new AnnotationCombination(Child.class, TextBody.class, Base64.class),
			new AnnotationCombination(Child.class, Attribute.class),
			new AnnotationCombination(Child.class, Attribute.class,
					Base64.class), };

	private MethodParameterUtils() {
	}

	public static <B> Object[] generateParameter(final Method method,
			final Element element, final String localName,
			final ParserContext<B> parserContext,
			final DefinitionBuilder definitionBuilder,
			final LyciaParser<B> lyciaParser) throws Exception {

		final Class<?>[] parameterTypes = method.getParameterTypes();
		final Annotation[][] annotations = method.getParameterAnnotations();

		final Object[] result = new Object[parameterTypes.length];

		for (int i = 0; i < parameterTypes.length; i++) {
			final Class<?> type = parameterTypes[i];

			final Annotation[] reordered = precheckAndReorderAnnotations(
					method, i, annotations[i], lyciaParser);

			Object value = null;
			if (reordered.length > 0) {
				final int size = reordered.length;
				int position = 0;

				for (final Annotation annotation : reordered) {
					ParameterMatcher matcher = getParameterMatcher(type,
							annotation, lyciaParser);

					if (matcher == null) {
						matcher = createParentAttributeMatcher(reordered, type,
								lyciaParser);
					}

					if (matcher != null) {
						final boolean moreAnnotations = position++ < size - 1;

						if (value instanceof Element) {
							value = getParameterObjectByType(type, annotation,
									matcher, value, (Element) value, localName,
									parserContext, definitionBuilder,
									lyciaParser, moreAnnotations);
						} else {
							value = getParameterObjectByType(type, annotation,
									matcher, value, element, localName,
									parserContext, definitionBuilder,
									lyciaParser, moreAnnotations);
						}

						if (value == null) {
							break;
						}
					}
				}

			} else {
				final ParameterMatcher matcher = getParameterMatcher(type,
						null, lyciaParser);

				value = getParameterObjectByType(type, null, matcher, null,
						element, localName, parserContext, definitionBuilder,
						lyciaParser, false);
			}

			if (lyciaParser.getConfigurator().isNullAware() && value == null
					&& !isAnnotationPresent(Nullable.class, annotations[i])) {

				throw new LyciaParsingException("Method " + method.getName()
						+ " in Class "
						+ method.getDeclaringClass().getSimpleName()
						+ " has a null parameter at index " + i);

			} else {
				result[i] = value;
			}
		}

		return result;
	}

	public static <B> Annotation[] precheckAndReorderAnnotations(
			final Method method, final int index,
			final Annotation[] annotations, final LyciaParser<B> lyciaParser) {

		if (annotations.length == 0) {
			return new Annotation[0];
		}

		final Annotation[] result = new Annotation[annotations.length];

		int pos = 0;
		final List<Annotation> lyciaAnnotations = new ArrayList<Annotation>();
		for (int i = 0; i < annotations.length; i++) {
			final Annotation annotation = annotations[i];

			if (!isLyciaParameterAnnotation(annotation)) {
				result[pos++] = annotation;
			} else {
				if (Attribute.class.equals(annotation.annotationType())) {
					lyciaAnnotations.add(buildAttributeByNamingStrategy(method,
							index, (Attribute) annotation, lyciaParser));
				} else {
					lyciaAnnotations.add(annotation);
				}
			}
		}

		final Annotation[] combination = reorderAnnotations(lyciaAnnotations);
		for (final Annotation annotation : combination) {
			result[pos++] = annotation;
		}

		return result;
	}

	public static <B> ParameterMatcher getParameterMatcher(final Class<?> type,
			final Annotation annotation, final LyciaParser<B> lyciaParser) {

		for (final ParameterMatcher matcher : lyciaParser.getConfigurator()
				.getPlatformFactory().newParameterMatcher()) {

			if (matcher.getParameterType().isAssignableFrom(type)) {
				if (matcher.getAnnotationType() == null) {
					return matcher;
				}

				if (matcher.getAnnotationType() != null && annotation != null) {
					if (matcher.getAnnotationType().equals(
							annotation.annotationType())) {
						return matcher;
					}
				}
			}
		}

		return null;
	}

	public static <B> Object getParameterObjectByType(final Class<?> type,
			final Annotation annotation, final ParameterMatcher matcher,
			final Object value, final Element element, final String localName,
			final ParserContext<B> parserContext,
			final DefinitionBuilder definitionBuilder,
			final LyciaParser<B> lyciaParser, final boolean moreAnnotations)
			throws Exception {

		if (matcher.getAnnotationType() == null) {
			if (Element.class.isAssignableFrom(type)) {
				return element;
			}

			if (String.class.isAssignableFrom(type)) {
				return localName;
			}

			if (QualifiedName.class.isAssignableFrom(type)) {
				return new QualifiedName(element.getNamespaceURI(), element
						.getLocalName());
			}

			if ("javax.xml.namespace.QName".equals(type.getCanonicalName())) {

				final Class<?> qNameClass = Class
						.forName("javax.xml.namespace.QName");
				final Constructor<?> constructor = qNameClass.getConstructor(
						String.class, String.class);

				return constructor.newInstance(element.getNamespaceURI(),
						element.getLocalName());
			}

			if (ParserContext.class.isAssignableFrom(type)) {
				return parserContext;
			}

			if (DefinitionBuilder.class.isAssignableFrom(type)) {
				return definitionBuilder;
			}
		}

		final Class<? extends Annotation> clazz = matcher.getAnnotationType();
		if (clazz.equals(Child.class)) {
			LOGGER.debug("Found @Child annotation to inject");
			return getChildElement(element, annotation, type, moreAnnotations,
					lyciaParser);

		} else if (clazz.equals(Parent.class)) {
			LOGGER.debug("Found @Parent annotation to inject");
			return getParentElement(element, type, (Parent) annotation,
					moreAnnotations);

		} else if (clazz.equals(ContextObject.class)) {
			LOGGER.debug("Found @ContextObject annotation to inject");
			return parserContext.getContextObject();

		} else if (clazz.equals(Base64.class)) {
			LOGGER.debug("Found @Base64 annotation to inject");
			final TypeConverter converter = ByteDataConverter.BASE_BYTE_DATA_CONVERTER;
			final Object encoded = (value != null ? value : getTextBody(
					element, String.class, null, lyciaParser));

			return converter.convert(encoded, type, lyciaParser);

		} else if (clazz.equals(TextBody.class)) {
			LOGGER.debug("Found @TextBody annotation to inject");
			final Object textBody = getTextBody(element, type,
					(TextBody) annotation, lyciaParser);

			if (String.class.isAssignableFrom(textBody.getClass())
					&& byte[].class.isAssignableFrom(type)) {

				return ((String) textBody).getBytes();

			} else {
				return textBody;
			}

		} else if (clazz.equals(Attribute.class)) {
			LOGGER.debug("Found @Attribute annotation to inject");
			return getConvertedAttributeValue(type, annotation, element,
					lyciaParser.getConfigurator());
		}

		return null;
	}

	private static <B> Object getTextBody(final Element element,
			final Class<?> type, final TextBody textBody,
			final LyciaParser<B> lyciaParser) throws Exception {

		final String value = lyciaParser.getConfigurator().getPlatformFactory()
				.newElementContentExtractor().getElementContent(element,
						textBody);

		if (textBody == null || value == null) {
			return value;
		}

		TypeConverter converter = null;
		if (textBody.converter().equals(TypeConverter.class)) {
			for (final TypeConverter dataConverter : lyciaParser
					.getConfigurator().getTypeConverters()) {

				if (dataConverter.accept(type)) {
					converter = dataConverter;
					break;
				}
			}

		} else {
			final Class<? extends TypeConverter> converterClass = textBody
					.converter();

			final Constructor<? extends TypeConverter> constructor = converterClass
					.getDeclaredConstructor();

			SetAccessible.setAccessible(constructor);
			converter = constructor.newInstance();

			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug("Converter instance: " + converter);
			}

		}

		if (converter == null) {
			throw new LyciaParsingException("No dataconverter found for type "
					+ type.getCanonicalName());
		}

		return converter.convert(value, type, lyciaParser);
	}

	private static boolean isExtendedAttribute(
			final Class<? extends Annotation> clazz) {

		if (clazz.getAnnotation(Attribute.class) != null) {
			return true;
		}

		return false;
	}

	private static Object getParentElement(final Element element,
			final Class<?> type, final Parent annotation,
			final boolean moreAnnotations) {

		Node parent = element.getParentNode();

		if (annotation.layers() > 1) {
			for (int i = 1; i < annotation.layers(); i++) {
				if (parent != null) {
					if (!(parent.getParentNode() instanceof Document)) {
						parent = parent.getParentNode();
					} else {
						throw new IllegalStateException(
								"Reached top of document-hierarchy, layers-count is incorrect");
					}
				} else {
					throw new NullPointerException(
							"Parent-Node is null, layers-count is incorrect");
				}
			}
		}

		if (Element.class.isAssignableFrom(type) || moreAnnotations) {
			return parent;
		}

		if (String.class.isAssignableFrom(type)) {
			if (parent != null) {
				return parent.getLocalName();
			}

			return null;
		}

		if (QualifiedName.class.isAssignableFrom(type)) {
			if (parent != null) {
				return new QualifiedName(parent.getNamespaceURI(), parent
						.getLocalName());
			}

			return null;
		}

		if ("javax.xml.namespace.QName".equals(type.getCanonicalName())) {
			if (!PlatformSupportUtils.isDalvikDependingPlatform()) {
				try {
					final Class<?> qNameClass = Class
							.forName("javax.xml.namespace.QName");
					final Constructor<?> constructor = qNameClass
							.getConstructor(String.class, String.class);

					return constructor.newInstance(parent.getNamespaceURI(),
							parent.getLocalName());
				} catch (final ClassNotFoundException e) {
					throw new LyciaConfigurationException(e);
				} catch (final SecurityException e) {
					throw new LyciaConfigurationException(e);
				} catch (final NoSuchMethodException e) {
					throw new LyciaConfigurationException(e);
				} catch (final IllegalArgumentException e) {
					throw new LyciaConfigurationException(e);
				} catch (final InstantiationException e) {
					throw new LyciaConfigurationException(e);
				} catch (final IllegalAccessException e) {
					throw new LyciaConfigurationException(e);
				} catch (final InvocationTargetException e) {
					throw new LyciaConfigurationException(e);
				}
			}
		}

		throw new LyciaConfigurationException(
				"@Parent annotation is used at illegal parameter-type. "
						+ "Only legal on String, QualifiedName, QName and Element");
	}

	private static <B> Object getChildElement(final Element element,
			final Annotation annotation, final Class<?> type,
			final boolean moreAnnotations, final LyciaParser<B> parser)
			throws Exception {

		final Child child = (Child) annotation;
		final List<Element> elements = getChildElementsByExpression(element,
				child);

		if (elements.size() == 0) {
			return null;
		}

		if (List.class.isAssignableFrom(type)) {
			if (Element.class.isAssignableFrom(child.listType())) {
				return elements;

			} else {
				TypeConverter converter = null;
				if (child.converter().equals(TypeConverter.class)) {
					for (final TypeConverter dataConverter : parser
							.getConfigurator().getTypeConverters()) {

						if (dataConverter.accept(child.listType())) {
							converter = dataConverter;
							break;
						}
					}

				} else {
					final Class<? extends TypeConverter> converterClass = child
							.converter();

					final Constructor<? extends TypeConverter> constructor = converterClass
							.getDeclaredConstructor();

					SetAccessible.setAccessible(constructor);
					converter = constructor.newInstance();

					if (LOGGER.isDebugEnabled()) {
						LOGGER.debug("Converter instance: " + converter);
					}

				}

				if (converter == null) {
					throw new LyciaParsingException(
							"No dataconverter found for type "
									+ child.listType().getCanonicalName());
				}

				final List<Object> result = new ArrayList<Object>();
				for (final Element el : elements) {
					final Object value = converter.convert(el,
							child.listType(), parser);

					if (value != null) {
						result.add(value);
					}
				}

				return result;
			}
		}

		if (elements.size() > 1) {
			throw new LyciaConfigurationException(
					"No unique element found for expression "
							+ child.value().value());
		}

		final Element result = elements.get(0);

		if (Element.class.isAssignableFrom(type) || moreAnnotations) {
			return result;
		}

		if (String.class.isAssignableFrom(type)) {
			if (element.getParentNode() != null) {
				return result.getLocalName();
			}

			return null;
		}

		if (QualifiedName.class.isAssignableFrom(type)) {
			return new QualifiedName(result.getNamespaceURI(), result
					.getLocalName());
		}

		if ("javax.xml.namespace.QName".equals(type.getCanonicalName())) {
			if (!PlatformSupportUtils.isDalvikDependingPlatform()) {
				try {
					final Class<?> qNameClass = Class
							.forName("javax.xml.namespace.QName");
					final Constructor<?> constructor = qNameClass
							.getConstructor(String.class, String.class);

					return constructor.newInstance(result.getNamespaceURI(),
							result.getLocalName());

				} catch (final ClassNotFoundException e) {
					throw new LyciaConfigurationException(e);
				} catch (final SecurityException e) {
					throw new LyciaConfigurationException(e);
				} catch (final NoSuchMethodException e) {
					throw new LyciaConfigurationException(e);
				} catch (final IllegalArgumentException e) {
					throw new LyciaConfigurationException(e);
				} catch (final InstantiationException e) {
					throw new LyciaConfigurationException(e);
				} catch (final IllegalAccessException e) {
					throw new LyciaConfigurationException(e);
				} catch (final InvocationTargetException e) {
					throw new LyciaConfigurationException(e);
				}
			}
		}

		throw new LyciaConfigurationException(
				"@Child annotation is used at illegal parameter-type. "
						+ "Only legal on String, QualifiedName, QName and Element");
	}

	private static List<Element> getChildElementsByExpression(
			final Element element, final Child child) {

		if (!element.hasChildNodes()) {
			return Collections.emptyList();
		}

		final Tag tag = child.value();

		if (tag.xpath() != null && !tag.xpath().expression().equals(XPath.NULL)) {
			throw new LyciaConfigurationException(
					"XPath expressions aren't supported on @Child constructs");
		}

		if (tag.regex()) {
			throw new LyciaConfigurationException(
					"Regex expressions aren't supported on @Child constructs");
		}

		if ("*".equals(tag.value())) {
			throw new LyciaConfigurationException(
					"CatchAll (*) constraints aren't supported on @Child constructs");
		}

		final String expression = TagConfigurationUtils
				.extractTagExpression(tag);
		final String namespaceURI = TagConfigurationUtils
				.extractTagNamespace(tag);

		QualifiedName[] qNames = new QualifiedName[1];
		if (expression.contains("\\")) {
			qNames = TagConfigurationUtils.generateParentQNames(expression,
					namespaceURI);
		} else {
			qNames[0] = new QualifiedName(namespaceURI, expression);
		}

		return recursiveFindElements(element, qNames, child.maxLayers(), 0);
	}

	private static List<Element> recursiveFindElements(final Element element,
			final QualifiedName[] qNames, final int maxLayers, int actualLayers) {

		if (maxLayers > -1 && actualLayers >= maxLayers) {
			return Collections.emptyList();
		}

		final List<Element> elements = new ArrayList<Element>();
		final NodeList nodes = element.getChildNodes();
		for (int i = 0; i < nodes.getLength(); i++) {
			final Node node = nodes.item(i);

			if (node.getNodeType() != Node.ELEMENT_NODE) {
				continue;
			}

			final Element el = (Element) node;

			if (TagConfigurationUtils.matchesElement(el, qNames)) {
				elements.add(el);
			} else if (el.hasChildNodes()) {
				elements.addAll(recursiveFindElements(el, qNames, maxLayers,
						actualLayers++));
			}
		}

		return elements;
	}

	private static <B> Object getConvertedAttributeValue(final Class<?> type,
			final Annotation annotation, final Element element,
			final LyciaConfigurator<B> configurator) throws Exception {

		final Attribute attribute = (Attribute) annotation;

		// Workaround for IBM JVMs
		final Attr attr = matchAttribute(element, attribute.namespace(),
				attribute.value());

		final String attributeValue = extractValue(attr, attribute, type);
		if (attributeValue == null) {
			return null;
		}

		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("Attribute: " + attr);
			LOGGER.debug("Final parametertype: " + type);
		}

		if (Attr.class.isAssignableFrom(type)) {
			if (attr != null) {
				return attr;
			} else {
				return new AttrImpl(element, attributeValue, attribute.value(),
						attribute.namespace(), null);
			}
		}

		TypeConverter converter = null;
		if (attribute.converter().equals(TypeConverter.class)) {
			for (final TypeConverter dataConverter : configurator
					.getTypeConverters()) {
				if (dataConverter.accept(type)) {
					converter = dataConverter;
					break;
				}
			}

		} else {
			final Class<? extends TypeConverter> converterClass = attribute
					.converter();

			final Constructor<? extends TypeConverter> constructor = converterClass
					.getDeclaredConstructor();

			SetAccessible.setAccessible(constructor);
			converter = constructor.newInstance();

			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug("Converter instance: " + converter);
			}

		}

		if (converter == null) {
			throw new LyciaParsingException("No dataconverter found for type "
					+ type.getCanonicalName());
		}

		return converter.convert(attributeValue, type);
	}

	private static Attr matchAttribute(final Element element,
			String namespaceURI, final String localName) {

		if (namespaceURI.equals(XMLConstants.NULL_NS_URI)) {
			namespaceURI = null;
		}

		final NamedNodeMap map = element.getAttributes();
		for (int i = 0; i < map.getLength(); i++) {
			final Node node = map.item(i);
			LOGGER.debug("Node found: " + node.getNamespaceURI() + "=>"
					+ node.getLocalName());

			if (node.getLocalName().equals(localName)) {
				if (node.getNamespaceURI() == null && namespaceURI == null
						|| node.getNamespaceURI().equals(namespaceURI)) {
					return (Attr) node;
				}
			}
		}

		return null;
	}

	private static Annotation[] reorderAnnotations(final List<Annotation> list) {
		Annotation[] annotations = new Annotation[list.size()];
		annotations = list.toArray(annotations);

		for (final AnnotationCombination combination : LEGAL_ANNOTATION_COMBINATIONS) {
			if (isLegalCombination(combination, annotations)) {
				final Annotation[] result = new Annotation[annotations.length];

				final Class<? extends Annotation>[] types = combination
						.getAnnotations();

				for (int i = 0; i < types.length; i++) {
					result[i] = getAnnotationByType(annotations, types[i]);
				}

				return result;
			}
		}

		final StringBuilder sb = new StringBuilder(
				"An illegal annotation combination was found [");
		sb.append(Arrays.toString(annotations)).append(']');
		throw new LyciaConfigurationException(sb.toString());
	}

	private static boolean isLegalCombination(
			final AnnotationCombination combination,
			final Annotation[] annotations) {

		if (annotations.length != combination.getAnnotations().length) {
			return false;
		}

		return recursiveIsLegalCombination(combination.getAnnotations(),
				annotations, 0);
	}

	private static boolean recursiveIsLegalCombination(
			final Class<? extends Annotation>[] types,
			final Annotation[] annotations, int index) {

		final Class<? extends Annotation> type = annotations[index]
				.annotationType();

		if (types[index].equals(type) || types[index].equals(Attribute.class)
				&& isExtendedAttribute(type)) {
			if (++index == annotations.length) {
				return true;
			}

			return recursiveIsLegalCombination(types, annotations, index);
		}

		return false;
	}

	private static boolean isLyciaParameterAnnotation(
			final Annotation annotation) {
		final Class<? extends Annotation> type = annotation.annotationType();

		if (type.equals(Child.class)) {
			return true;
		}

		if (type.equals(Parent.class)) {
			return true;
		}

		if (type.equals(Base64.class)) {
			return true;
		}

		if (type.equals(Attribute.class)) {
			return true;
		}

		if (isExtendedAttribute(type)) {
			return true;
		}

		if (type.equals(TextBody.class)) {
			return true;
		}

		if (type.equals(ContextObject.class)) {
			return true;
		}

		return false;
	}

	private static <B> Attribute buildAttributeByNamingStrategy(
			final Method method, final int index, final Attribute attribute,
			final LyciaParser<B> lyciaParser) {

		final AttributeNameFindingStrategy<B> strategy = lyciaParser
				.getConfigurator().getPlatformFactory()
				.newAttributeNameFindingStrategy();

		final String name = strategy.findAttributeName(method, index);

		return new Attribute() {
			@Override
			public Class<? extends Annotation> annotationType() {
				return Attribute.class;
			}

			@Override
			public String value() {
				return name;
			}

			@Override
			public String namespace() {
				return attribute.namespace();
			}

			@Override
			public Class<? extends TypeConverter> converter() {
				return attribute.converter();
			}

			@Override
			public String defaultValue() {
				return attribute.defaultValue();
			}
		};
	}

	@SuppressWarnings("unchecked")
	private static <A extends Annotation> A getAnnotationByType(
			final Annotation[] annotations, final Class<A> annotationClass) {

		for (final Annotation a : annotations) {
			if (a.annotationType().equals(annotationClass)) {
				return (A) a;

			} else if (isExtendedAttribute(a.annotationType())) {
				LOGGER.debug("Found extended @Attribute annotation '"
						+ a.annotationType().getName() + "'");

				return (A) a.annotationType().getAnnotation(Attribute.class);
			}
		}

		return null;
	}

	private static boolean isAnnotationPresent(
			final Class<? extends Annotation> annotationType,
			final Annotation[] annotations) {

		for (final Annotation annotation : annotations) {
			if (annotation.annotationType() == annotationType) {
				return true;
			}
		}

		return false;
	}

	private static String extractValue(final Attr attr,
			final Attribute attribute, final Class<?> type) {

		if (attr != null) {
			return attr.getValue();
		}

		if (!attribute.defaultValue().equals(Attribute.NULL)) {
			return attribute.defaultValue();
		}

		if (type.equals(boolean.class)) {
			return String.valueOf(Boolean.FALSE);
		}

		if (isPrimitiveNumberType(type)) {
			return String.valueOf(0);
		}

		return null;
	}

	private static boolean isPrimitiveNumberType(final Class<?> type) {
		return type.equals(byte.class) || type.equals(short.class)
				|| type.equals(int.class) || type.equals(long.class)
				|| type.equals(float.class) || type.equals(double.class);
	}

	private static <B> ParameterMatcher createParentAttributeMatcher(
			final Annotation[] annotations, final Class<?> type,
			final LyciaParser<B> lyciaParser) {

		boolean parentFound = false;
		for (final Annotation annotation : annotations) {
			if (annotation.annotationType().equals(Parent.class)) {
				parentFound = true;
			}
		}

		if (!parentFound) {
			return null;
		}

		for (final Annotation annotation : annotations) {
			if (annotation.annotationType().equals(Attribute.class)) {
				if (getParameterMatcher(type, annotation, lyciaParser) != null) {
					return new ParameterMatcher(type, Parent.class);
				}
			}
		}

		return null;
	}

}
